# -*- coding: utf-8 -*-
import json,os,logging,traceback, time, threading
from jinja2 import Markup
from flask import flash, url_for, redirect, request, current_app, jsonify
from flask_admin.base import expose
from flask_admin.model.template import EndpointLinkRowAction, LinkRowAction
from flask_wtf import FlaskForm
from sqlalchemy import func
from wtforms import SelectMultipleField, ValidationError, TextField, HiddenField, TextAreaField, SelectField, BooleanField, StringField, validators, DateTimeField, DateField
from wtforms.ext.sqlalchemy.fields import QuerySelectField
# from wtforms.fields.html5 import DateTimeField
from wtforms.validators import DataRequired
from flask_admin.actions import action
from flask_admin.helpers import (get_form_data, validate_form_on_submit,get_redirect_target, flash_errors)
from flask_admin.contrib.sqla import form, filters as sqla_filters, tools
from . import MyModelView
from . import AjaxQueryIndexLike
from adminApp.extensions import db, redis_store
from adminApp.models import Machine, NetWorkCard, TaskLog, Idc, DefaultNicFlowData as dnf
from datetime import datetime, timedelta, date
from adminApp.utils.telnet_util import check_ip_for_telnet
from dateutil.relativedelta import relativedelta
from adminApp.utils.JsonToDatetime import MyEncoder



logger = logging.getLogger(__name__)


def get_flow(size):
    power = 2**10
    n = 0
    Dic_powerN = {0 : '', 1: 'k', 2: 'M', 3: 'G', 4: 'T', 5: 'P'}
    while size > power:
        size /= power
        n += 1
    return '{}{}'.format(round(size,2), Dic_powerN[n]+'Bit/s')


def get_flow_dw(size):
    power = 2**10
    n = 0
    Dic_powerN = {0 : 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB', 5: 'PB'}
    while size > power:
        size /= power
        n += 1
    if n>5:
        return 4, 'PB'
    return n, Dic_powerN[n]


def get_flow_dw_d(p, size):
    power = 1024**p
    size /= power
    return round(size, 2)


class BatchConfigFrm(FlaskForm):

    def query_factory():
        return db.session.query(Idc).all()
        # return [r.name for r in db.session.query(Idc).all()]

    def get_pk(obj):
        return obj

    ids = HiddenField()
    returnurl = HiddenField()
    idc = QuerySelectField(label='机房', render_kw={'class': 'form-control'}, query_factory=query_factory, get_pk=get_pk, allow_blank=True, blank_text='请选择')
    man_mark = StringField(label='标识', render_kw={'placeholder': '自定义标识'})
    aqy_cert_status = BooleanField(label='认证状态', render_kw={'placeholder': '认证状态'})
    aqy_cert_date = StringField(label='认证日期', render_kw={'data-date-format':"YYYY-MM-DD HH:mm:ss", 'data-role':"datetimepicker", 'placeholder': '认证日期'})
    # aqy_cert_date = DateTimeField(label='认证日期', validators=[validators.DataRequired('asdfasdf')])
    is_trigger_warn = BooleanField(label='触发警报', render_kw={'placeholder': '触发警报'}, default=True)
    is_auto_update_program = BooleanField(label='自动更新', default=True)
    remark = TextAreaField(label='备注', render_kw={'placeholder': '备注', 'rows': '5'})


class MachineView(MyModelView):
    fast_mass_delete = True
    can_create = False
    edit_modal = True
    details_modal = True
    column_filters = (
        'idc.name', 'idc.mark', 'man_mark', 'hostname', 'system_uuid', 'aqy_device_id', 'ip_num', 'is_line',
        'is_virtual', 'is_pve', 'is_centos', 'port_8400_chk', 'system_type', 'system_product_name',
        'aqy_cert_status', 'aqy_cert_date', 'default_nic',
        'is_trigger_warn', 'is_auto_update_program', 'remark')
    # 'aqy_cert_status','aqy_cert_date',
    column_list = ('id', 'idc.name', 'idc.mark', 'default_nic_rx','default_nic_tx', 'hostname', 'man_mark', 'aqy_device_id',
                   'default_nic','def_nic_nat_type','def_nic_wan_ip','aqy_band_width','multi_line_scene','only_kcp',
                   'ip_num', 'is_line', 'port_8400_chk', 'port_chk_err_num', 'port_8400_chk_last')

    # column_sortable_list = (
    #     'id', 'name', 'category.name', 'author.name', 'serial_state', 'words', 'last_updated', 'created')
    column_toggle_control_list = ['aqy_cert_status','is_virtual','is_pve','is_centos','is_line','is_trigger_warn','is_auto_update_program']
    column_editable_list = ('aqy_cert_status', 'aqy_cert_date', 'remark', 'man_mark','is_trigger_warn','is_auto_update_program')

    column_labels = {
        'idc': '机房',
        'idc.mark':'机房分段',
        'default_nic_rx':'接收',
        'default_nic_tx':'发送',
        'flow': '下行/上行',
        'hostname': '主机名称',
        'idc.name': '机房名称',
        'man_mark': '设备标识',
        'man_confirm': '确认状态',
        'system_uuid': '系统ID',
        'aqy_device_id': '爱奇艺ID',
        'aqy_cert_status': '认证状态',
        'aqy_cert_date': '认证日期',
        'system_type': '系统类型',
        'system_product_name': '设备类型',
        'is_virtual': '虚拟机',
        'is_pve': 'PVE',
        'is_centos': 'CentOS',
        'default_nic': '默认接口',
        'ip_num': 'IP数量',
        'is_line': '在线',
        'is_trigger_warn': '警报',
        'is_auto_update_program': '自动更新',
        'kernel_version':'内核版本',
        'netfaces': '网络接口信息',
        'gateways': '路由信息',
        'virsh_accounts': '内置拨号信息',
        'exportstatus_8400': '8400IP状态',
        'client_version': '客户端版本',
        'remark': '备注',
        'created': '创建日期',
        'updated': '更新日期',
        'port_8400_chk': '8400状态',
        'port_8400_chk_last': '测试日期',
        'port_chk_err_num':'错误',
        'def_nic_nat_type':'NAT',
        'def_nic_wan_ip':'WanIP',
        'aqy_band_width':'带宽',
        'multi_line_scene':'多线',
        'only_kcp':'kcp'
    }

    column_formatters = dict(
        default_nic_rx=lambda v, c, m, p: Markup(
            '<div id="fw_down_{id}" data-uuid="{id}" type="flow" class="text-danger" title="接收">--</div>'.format(id=m.system_uuid)),
        default_nic_tx=lambda v, c, m, p: Markup(
            '''<div id="fw_up_{uid}" class="text-success" onclick="show_flow_data('{id}')" style="cursor:pointer;" title="发送">--</div>'''.format(uid=m.system_uuid,id=m.id)),
        created=lambda v, c, m, p: m.created.strftime("%Y-%m-%d %H:%M:%S"),
        aqy_cert_date=lambda v, c, m, p: m.aqy_cert_date.strftime("%Y-%m-%d %H:%M:%S") if m.aqy_cert_date else '',
        updated=lambda v, c, m, p: m.updated.strftime("%Y-%m-%d %H:%M:%S") if m.updated else '',
        port_8400_chk_last = lambda v, c, m, p: m.port_8400_chk_last.strftime("%Y-%m-%d %H:%M") if m.port_8400_chk_last else ''
    )
    form_columns = ('idc','aqy_cert_status', 'aqy_cert_date', 'remark', 'man_mark','is_trigger_warn','is_auto_update_program')
    list_template = 'machine/machine_list.html'
    column_choices = {
        'port_8400_chk': [('ok', 'OK'), ('failed', 'Failed'), ('noip', 'NoIP'), ('noserver', 'NoServer'), ('error', 'Error')]
    }
    @expose('/get_cur_flow', methods=('POST',))
    def get_cur_flow(self):
        result = dict()
        sids = request.form.get('uuids')
        ids = sids.split(',') if sids else []
        for uuid in ids:
            rtx = redis_store.get(current_app.config.get('RD_DEF_NETIO').format(uuid))
            if rtx:
                try:
                    d=json.loads(rtx)
                    result[uuid] = [get_flow((d[0] if d[0] else 0) * 8), get_flow((d[1] if d[1] else 0) * 8)]
                except:
                    result[uuid]=['--', '--']
            else:
                # logger.info('get redis rtx none : {}'.format(rtx))
                result[uuid] = ['--', '--']
        return result

    def last_day_of_month(self, any_day):
        """
        获取获得一个月中的最后一天
        :param any_day: 任意日期
        :return: string
        """
        next_month = any_day.replace(day=28) + timedelta(days=4)  # this will never fail
        return next_month - timedelta(days=next_month.day)

    @expose('/statis_flow_graph', methods=('GET', 'POST',))
    def statis_flow_graph(self):
        mid = request.args.get('id')
        if not mid:
            mid = 0
        # print('statis_flow_graph ', mid)
        def_ten_minute = "{} - {}".format(date.today(), date.today())
        def_hour = "{} - {}".format(date.today(),date.today())
        def_day = "{} - {}".format(date.today().replace(day=1), self.last_day_of_month(date.today()))
        def_month = "{} - {}".format(date.today().replace(month=1).strftime('%Y-%m'),date.today().replace(month=12).strftime('%Y-%m'))
        return self.render('machine/statis_flow_graph.html', mid=mid, minute=def_ten_minute, hour=def_hour, day=def_day, month=def_month)

    @expose('/get_flow_graph', methods=('GET', 'POST',))
    def get_flow_graph(self):
        mid = request.args.get('id')
        if not mid:
            mid = 0
        # print('get_flow_graph ', mid)
        ret_type = request.args.get('type')  # ten minuter, hour, day, month
        date_range = request.args.get('date_range')
        start, end = date_range.split(' - ') if date_range else (None, None)
        cur_d = datetime.now()
        if not mid:
            return redirect(request.referrer)
        if 'hour' == ret_type:
            start = datetime.strptime(start, '%Y-%m-%d') if start else datetime(cur_d.year, cur_d.month, cur_d.day)
            fq = db.session.query(func.sum(dnf.ten_bt_sent).label('bt_sent'),
                                  func.sum(dnf.ten_bt_recv).label('bt_recv'),
                                  func.date_format(dnf.flow_datetime, "%Y-%m-%d %H").label('td')) \
                .filter(dnf.machine_id == mid) \
                .group_by(func.date_format(dnf.flow_datetime, "%Y-%m-%d %H")).order_by(
                func.date_format(dnf.flow_datetime, "%Y-%m-%d %H"))
            fq = fq.filter(dnf.flow_datetime >= start)
            if end:
                end = datetime.strptime(end, '%Y-%m-%d') + timedelta(days=1)
                fq = fq.filter(dnf.flow_datetime < end)
        elif 'day' == ret_type:
            start = datetime.strptime(start, '%Y-%m-%d') if start else datetime(cur_d.year, cur_d.month, cur_d.day)
            fq = db.session.query(func.sum(dnf.ten_bt_sent).label('bt_sent'),
                                  func.sum(dnf.ten_bt_recv).label('bt_recv'),
                                  func.date_format(dnf.flow_datetime, "%Y-%m-%d").label('td')) \
                .filter(dnf.machine_id == mid) \
                .group_by(func.date_format(dnf.flow_datetime, "%Y-%m-%d")) \
                .order_by(func.date_format(dnf.flow_datetime, "%Y-%m-%d"))
            fq = fq.filter(dnf.flow_datetime >= start)
            if end:
                end = datetime.strptime(end, '%Y-%m-%d') + timedelta(days=1)
                fq = fq.filter(dnf.flow_datetime < end)
        elif 'month' == ret_type:
            start = datetime.strptime(start, '%Y-%m') if start else date.today().replace(day=1)
            fq = db.session.query(func.sum(dnf.ten_bt_sent).label('bt_sent'),
                                  func.sum(dnf.ten_bt_recv).label('bt_recv'),
                                  func.date_format(dnf.flow_datetime, "%Y-%m").label('td')) \
                .filter(dnf.machine_id == mid) \
                .group_by(func.date_format(dnf.flow_datetime, "%Y-%m")).order_by(
                func.date_format(dnf.flow_datetime, "%Y-%m-%d"))
            fq = fq.filter(dnf.flow_datetime >= start)
            if end:
                end = datetime.strptime(end, '%Y-%m') + relativedelta(month=1)
                fq = fq.filter(dnf.flow_datetime < end)
        else:
            # ten use this
            start = datetime.strptime(start, '%Y-%m-%d') if start else datetime(cur_d.year, cur_d.month, cur_d.day)
            fq = db.session.query(dnf.ten_bt_sent.label('bt_sent'), dnf.ten_bt_recv.label('bt_recv'),
                                  dnf.flow_datetime.label('td')) \
                .filter(dnf.machine_id == mid) \
                .order_by(dnf.flow_datetime)
            fq = fq.filter(dnf.flow_datetime >= start)
            if end:
                end = datetime.strptime(end, '%Y-%m-%d') + timedelta(days=1)

                fq = fq.filter(dnf.flow_datetime < end)

        result = dict()
        result['data'] = []
        data = fq.all()
        max = 0
        for r in data:
            if r[0]>max:
                max = r[0]
        dw_size, dw_name = get_flow_dw(max)

        #get_flow_dw
        # 转换为MB GB TB
        for r in data:
            # r[0]/1024.0/1024/1024  GB
            # r[0] / 1024.0 / 1024 / 1024/1024   TB
            if ret_type in ['hour', 'day', 'month']:
                #result['data'].append((get_flow_dw_d(dw_size, r[0]), get_flow_dw_d(dw_size, r[1]), r[2].strftime("%Y-%m-%d %H:%M:%S")))
                result['data'].append((get_flow_dw_d(dw_size, r[0]), get_flow_dw_d(dw_size, r[1]), r[2]))
            else:
                #result['data'].append((get_flow_dw_d(dw_size, r[0]), get_flow_dw_d(dw_size, r[1]), r[2].strftime("%Y-%m-%d %H:%M:%S")))
                result['data'].append((get_flow_dw_d(dw_size, r[0]), get_flow_dw_d(dw_size, r[1]), r[2]))
        result['dw'] = dw_name
        # print(result)
        return jsonify(result)

    def after_model_delete(self, model):
        redis_store.delete(current_app.config.get('RD_MACHINE_EXISTS').format(model.system_uuid))

    @expose('/batch_config', methods=('GET', 'POST'))
    def batch_config(self):
        ids = request.args.get('ids')
        form = BatchConfigFrm(request.form)
        if request.method == 'POST' and form.validate():
            try:
                isum = 0
                for r in db.session.query(Machine).filter(Machine.id.in_(form.ids.data.split(','))):
                    isum += 1
                    r.idc = form.idc.data
                    r.man_mark = form.man_mark.data
                    # r.aqy_cert_date = None
                    r.aqy_cert_date = form.aqy_cert_date.data if form.aqy_cert_date.data else None
                    r.aqy_cert_status = form.aqy_cert_status.data
                    r.is_trigger_warn = form.is_trigger_warn.data
                    r.is_auto_update_program = form.is_auto_update_program.data
                    r.remark = form.remark.data
                db.session.commit()
                flash('批量配置记录，更新了{}条记录'.format(isum), 'success')
                if form.returnurl.data:
                    return redirect(form.returnurl.data)
                else:
                    return redirect(url_for('.index_view'))
            except:
                flash('执行失败:{}'.format(traceback.format_exc()), 'error')
        else:
            form.ids.data = ids
            form.is_trigger_warn.data=True
            form.is_auto_update_program.data = True
            form.returnurl = request.referrer
            # request.referrer
        return self.render('machine/batch_config.html', form=form)


    @action('add_task_system_info', '添加获取系统信息任务', '确定将选中的设备添加获取系统信息任务吗?')
    def action_add_task_system_info(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(TaskLog(is_sytem_build=False, command_text="system_info", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('add_task_restart_client', '添加客户端重启任务', '确定将选中的设备添加客户端重启任务吗?')
    def action_add_task_restart_client(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(TaskLog(is_sytem_build=False, command_text="restart_client", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('add_task_auto_update', '添加自动更新任务', '确定将选中的设备添加客户端自动更新任务吗?')
    def action_add_task_auto_update(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(TaskLog(is_sytem_build=False, command_text="auto_update", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('add_task_uninstall', '卸载客户端删除设备记录', '确定将选中的设备卸载客户端并删除设备记录吗?')
    def action_add_task_uninstall(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(TaskLog(is_sytem_build=False, command_text="uninstall", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('check_port_8400', '测试设备8400端口', '确定测试选中的设备8400端口tcp到达状态吗?')
    def action_check_port_8400(self, ids):
        def check_port(ip, port, id, drc):
            drc['{}_{}_{}'.format(ip, port, id)] = check_ip_for_telnet(ip, port)
        try:
            ichks = 0
            chkerrs = 0
            iall = 0
            noips = 0
            noservers= 0
            theads = []
            result = dict()
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            for m in query.all():
                iall += 1
                if m.is_centos:
                    try:
                        if m.exportstatus_8400:
                            eps = json.loads(m.exportstatus_8400)
                            ips = eps.get('exportstatus',{}).get('host', [])
                            if ips:
                                for i_p in ips:
                                    ip, port = i_p.split(':')
                                    t1 = threading.Thread(target=check_port, args=(ip, 8400, m.id, result), daemon=True)
                                    t2 = threading.Thread(target=check_port, args=(ip, port, m.id, result), daemon=True)
                                    theads.append(t1)
                                    theads.append(t2)
                                    t1.start()
                                    t2.start()
                            else:
                                m.port_8400_chk = 'noip'
                                noips += 1
                        else:
                            m.port_8400_chk = 'noserver'
                            noservers += 1
                    except:
                        m.port_8400_chk = 'error'
                        chkerrs +=1
                    m.port_8400_chk_last = datetime.now()
                    m.port_chk_result = None
                    m.port_chk_err_num = 0
                else:
                    pass
            db.session.commit()
            for t in theads:
                t.join()
            result2 = dict()
            for k, v in result.items():
                ip, port, id = k.split('_')
                if not result2.get(id):
                    result2[id] = dict(allchk=0, errors=0, errlist=[])
                result2[id]['allchk'] += 1
                if 'failed' == v:
                    result2[id]['errors'] += 1
                    result2[id]['errlist'].append('{}:{}'.format(ip, port))
            ichks = 0
            for k, v in result2.items():
                ichks += v.get('allchk')
                chkerrs += v.get('errors',0)
                db.session.query(Machine).filter(Machine.id == k).update({'port_8400_chk': 'ok' if v.get('allchk')>v.get('errors') else 'failed',
                                                                          'port_chk_result': json.dumps(v),
                                                                          'port_chk_err_num': v.get('errors')})
            db.session.commit()
            flash('共{}记录，检测{}条，无服务数据{}条，无IP记录{}条，错误{}条'.format(iall,ichks,noservers,noips,chkerrs), 'success')
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('add_task_hd_info', '添加差异更新95值记录任务', '确定将选中的设备差异更新95值记录任务?')
    def action_add_task_hd_info(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(TaskLog(is_sytem_build=False, command_text="dif_upd_bandwidth95", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')

    @action('add_get_all_snmp', '获取全部snmplog', '确定将选中的设备获取全部snmplog?')
    def add_get_all_snmp(self, ids):
        try:
            query = tools.get_query_for_ids(self.get_query(), self.model, ids)
            uuids = []
            for m in query.all():
                m.task_logs.append(
                    TaskLog(is_sytem_build=False, command_text="getsnmp201231", command_type="mark"))
                uuids.append(m.system_uuid)
            self.session.commit()
            for uuid in uuids:
                redis_store.rpush(current_app.config.get('RD_WILL_TASK_ID'), uuid)
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise
            flash('执行失败:{}'.format(ex), 'error')